 package olegroshka.src.com.bofa.olegroshka.analitics;

import java.math.BigDecimal;
import java.util.Comparator;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.TreeMap;

/**
 * Responsible for building new and valid instances of RefundModel. 
 * 
 * @author Oleg
 *
 */
public class RefundModelBuilder {
	
	private final static Logger LOG = Logger.getLogger(RefundModelBuilder.class.getName());
	
	//private static final String REFUND_LEVEL_REGEXP = "refund\\.level[\\d+]\\.amount\\.(min|max)";

	private static class RangeDiscountModelImpl extends RangeRefundModelBase {

		private static final BigDecimal HUNDRED = BigDecimal.TEN.multiply(BigDecimal.TEN);
		
		private final TreeMap<BigDecimal, DiscountLevelDescriptor> discountTreeMap;
		
		private RangeDiscountModelImpl() {
			this.discountTreeMap = new TreeMap<BigDecimal, DiscountLevelDescriptor>(
					new Comparator<BigDecimal>() {
						public int compare(BigDecimal arg0, BigDecimal arg1) {
							return arg0.compareTo(arg1);
						}						
					});
		}

		void addDiscountLevelInfo(BigDecimal min, BigDecimal max, BigDecimal discountRate) {
			
			if( min == null || min.signum() == -1) {
				throw new IllegalArgumentException("min can not be null or negative");
			}
			if( max == null || max.signum() == -1 ) {
				throw new IllegalArgumentException("max can not be null or negative");
			}
			if( discountRate == null ) {
				throw new IllegalArgumentException("discountRate can not be null");
			}
			if( HUNDRED.compareTo(discountRate) < 0 || BigDecimal.ZERO.compareTo(discountRate) > 0 ) {
 				throw new IllegalArgumentException("discountRate rate must be in the range from 0.00 to 100.00");
			}
			if( min.compareTo(max) >= 0 ) {
				throw new IllegalArgumentException("max must be more than min");
			}
			
			DiscountLevelDescriptor floorDiscountLevel = getDiscountLevelInfo(min);
			if( floorDiscountLevel != null && 
					( floorDiscountLevel.getMax().compareTo(min) > 0 || 
							discountTreeMap.ceilingKey(min) != null )) {
				throw new IllegalArgumentException("Discount level intervals can not overlap, " +
						"can not add min: " + min + ", max: " + max + " to the existing: " + floorDiscountLevel);
			}
			
			BigDecimal rate = discountRate.divide(HUNDRED);
			DiscountLevelDescriptor discountLevelDescriptor;
			if (floorDiscountLevel != null) {
				BigDecimal previousLevelDiscount = floorDiscountLevel.getDiscount();//max.add(min.negate()).multiply(rate);
				BigDecimal cumulativeDiscountFactor = 
						floorDiscountLevel.getCumulativeDiscountValue().add(previousLevelDiscount);
				discountLevelDescriptor = new DiscountLevelDescriptor(
						min, 
						max,
						rate,
						cumulativeDiscountFactor);				
			} else {
				discountLevelDescriptor = new DiscountLevelDescriptor(
						min, 
						max,
						rate, 
						BigDecimal.ZERO);

			}
			
			discountTreeMap.put(min, discountLevelDescriptor);
		}

		@Override
		protected DiscountLevelDescriptor getDiscountLevelInfo(BigDecimal salesAmount) {
			Entry<BigDecimal, DiscountLevelDescriptor> floorEntry = 
					discountTreeMap.floorEntry(salesAmount);
			
			return floorEntry != null ? floorEntry.getValue() : null;
		}
		 
	} 
	
	private final RangeDiscountModelImpl rangeDiscountModelTmpl = new RangeDiscountModelImpl();
	
	public RefundModelBuilder addDiscountLevel(BigDecimal min, BigDecimal max, BigDecimal rate) {
		rangeDiscountModelTmpl.addDiscountLevelInfo(min, max, rate);
		return this;
	}
	
	public RefundModelBuilder addRefundLevel(String min, String max, String rate) {
		rangeDiscountModelTmpl.addDiscountLevelInfo(new BigDecimal(min), new BigDecimal(max), new BigDecimal(rate));
		return this;
	}
	
	public RefundModelBuilder addRefundLevelsFromProperties(Properties properties) {
		for (int i = 1; true; i++) {
			String minKey = String.format("refund.level%d.amount.min", i);
			String minText = properties.getProperty(minKey);
			
			String maxKey = String.format("refund.level%d.amount.max", i);
			String maxText = properties.getProperty(maxKey);
			
			String rateKey = String.format("refund.level%d.percent", i);
			String rateText = properties.getProperty(rateKey);
			
			boolean configurationComplete = ( (minText != null) & (rateText != null) ); 
			boolean configurationNotSpecified = ( (minText == null) & (maxText == null) & (rateText == null) ); 
			if ( !configurationNotSpecified && !configurationComplete) {
				throw new IllegalStateException(
						"Partial configuration for discount "
								+ "levels specified, decimal values (e.g. 10.99) " +
								"for keys: " + minKey + ", " + maxKey + ", " + rateKey
								+ " are required.");
			} else if (i == 1 && configurationNotSpecified) {
				warnMissingConfigKey(minKey, minText);
				warnMissingConfigKey(rateKey, rateText);
				warnMissingConfigKey(rateKey, rateText);
			} else if (configurationNotSpecified) {
				break;
			} else {
				if( maxText == null ) { //last discount level
					addDiscountLevel(
							parseBigDecimal(minKey, minText),
							BigDecimal.valueOf(Double.MAX_VALUE), 
							parseBigDecimal(rateKey, rateText));
					break;
				} else {
					addDiscountLevel(
							parseBigDecimal(minKey, minText),
							parseBigDecimal(maxKey, maxText), 
							parseBigDecimal(rateKey, rateText));
				}
			}
		}
		
		return this;
	}

	private BigDecimal parseBigDecimal(String key, String text) {
		BigDecimal min = null;
		try {
			min = new BigDecimal(text);
		} catch (Exception e) {
			String msg = "Error while loading configuration, " +
					"can not parse: " + key + " for key: " + key;
			LOG.log(Level.SEVERE, msg, e);
			throw new IllegalStateException(msg, e);
		}
		return min;
	}

	private void warnMissingConfigKey(String minKey, String min) {
		if (min == null) {
			LOG.warning("No configuration for discount levels found, missing key: " + minKey);
		}
	}
	 
	public RefundModelBuilder clearDiscountData() {
		rangeDiscountModelTmpl.discountTreeMap.clear();
		return this;
	}
	
	public RefundModel buildRangeModel() {
		RangeDiscountModelImpl rangeDiscountModel = new RangeDiscountModelImpl();
		rangeDiscountModel.discountTreeMap.putAll(rangeDiscountModelTmpl.discountTreeMap);
		
		return rangeDiscountModelTmpl;
	}
	
}
